#!/usr/bin/env python3.11
# -*- encoding: utf-8 -*-
"""
@File    :   controllers.py
@Time    :   2024/05/28 10:21:25
@Author  :   windstarry 
@Version :   1.0
"""
# here put the import lib
from datetime import datetime

from django.contrib.auth import get_user_model, authenticate
from django.forms.models import model_to_dict
from django.contrib.auth.hashers import make_password
from ninja_extra import api_controller, route, status
from ninja_extra.permissions import IsAuthenticated, IsAdminUser

from ninja_jwt import schema
from ninja_jwt.authentication import JWTAuth
from ninja_jwt.controller import TokenObtainSlidingController
from ninja_jwt.tokens import SlidingToken
from ninja_extra.exceptions import APIException
from ninja_extra.pagination import (
    PageNumberPaginationExtra,
    paginate,
)
from core.schemas import MsgSchema,DataSchema
from .schema import (
    LoginSchema,
    UserInfoSchema,
    UserRoleSchema,
    UserInSchema,
    UserUpdateSchema,
    UserTokenOutSchema,
    RoleSchema,
    RoleInSchema,
)
from .mixins import RoleViewMixin, UserViewMixin
from .models import UserRoleModel
from apps.menus.models import RoleMenuModel
from core.utils import list_to_tree

User = get_user_model()


@api_controller("/users", tags=["用户管理"], auth=JWTAuth())
class UserController(UserViewMixin):
    @route.get(
        "/info",
        summary="用户信息",
        url_name="userinfo",
        response={201: UserRoleSchema},
        permissions=[IsAuthenticated],
    )
    def get_user_info(
        self,
    ):
        """
        获取当前请求用户的信息及其角色。

        该方法通过当前请求上下文获取用户对象，然后提取该用户的非禁用角色信息，
        将用户信息和角色ID列表封装成UserRoleSchema对象并返回。

        :return: UserRoleSchema对象, 包含用户信息和角色ID列表。
        """
        # 从请求上下文中获取用户对象
        user_obj = self.context.request.user

        # 筛选出状态不为9（表示禁用）的角色，并收集其ID
        roles = [obj.role.id for obj in user_obj.roles.exclude(status=9).all()]

        # 返回封装了用户信息和角色ID的UserRoleSchema对象
        return UserRoleSchema(user=user_obj, roles=roles)
    
    
    @route.get(
        "/{uid}",
        summary="用户详情",
        url_name="user-detail",
        response={201: UserRoleSchema},
        permissions=[IsAuthenticated]
    )
    def get_user_info_by_id(self,uid: int):
        user_obj = self.get_object_or_exception(
            self.get_queryset(), id=uid, error_message="用户不存在"
        )
        # 筛选出状态不为9（表示禁用）的角色，并收集其ID
        roles = [obj.role.id for obj in user_obj.roles.exclude(status=9).all()]
        # 返回封装了用户信息和角色ID的UserRoleSchema对象
        return UserRoleSchema(user=user_obj, roles=roles)
        

    @route.get(
        "",
        summary="用户列表",
        url_name="user-list",
        response=PageNumberPaginationExtra.get_response_schema(UserInfoSchema),
    )
    @paginate(PageNumberPaginationExtra, page_size=10)
    def get_user_list(
        self,
    ):
        return self.get_queryset()

    @route.post(
        "",
        summary="创建用户",
        url_name="create_user",
        response=[(201, UserInfoSchema), (400, MsgSchema)],
        permissions=[IsAdminUser],
    )
    def create_user(self, data: UserInSchema):
        rids = data.roles
        del data.roles
        user = data.create()
        for rid in rids:
            UserRoleModel.objects.create(user=user, role_id=rid)
        return 201, user

    @route.delete(
        "/{uid}", response={200: MsgSchema}, summary="删除用户", url_name="user-delete"
    )
    def delete_user(self, uid: int):
        user = self.get_object_or_exception(
            self.get_queryset(),
            id=uid,
            error_message="用户不存在",
        )
        user.delete()
        return 200, dict(detail="删除成功")

    @route.generic(
        "/{uid}",
        methods=["PUT"],
        response={200: UserRoleSchema},
        summary="修改用户",
        url_name="user-update",
    )
    def update_user(self, uid: int, data: UserUpdateSchema):
        rids = [role.rid for role in data.roles]
        del data.roles
        user = self.get_object_or_exception(
            self.get_queryset(), id__exact=uid, error_message="用户不存在"
        )
        # 判断密码是否修改
        if data.password != "加密之后的密码":
            # 加密密码
            data.password = make_password(data.password)
        else:
            del data.password
        # 增加modifier
        user.modifier = self.context.request.user
        data.update(user)
        # 将user未在rids的角色信息status修改为9
        UserRoleModel.objects.filter(user=user).exclude(role_id__in=rids).update(
            status=9
        )
        for rid in rids:
            UserRoleModel.objects.update_or_create(user=user, role_id=rid)
        return 200, UserRoleSchema(user=user, roles=rids)

    @route.put(
        "/role/{rid}",
        summary="切换用户角色",
        url_name="user-role-change",
        response=[(200, MsgSchema), (400, MsgSchema)],
    )
    def user_change_role(self, rid: int):
        user = self.context.request.user
        # 判断角色是否存在
        if not UserRoleModel.objects.filter(user=user, role_id=rid).exists():
            return 400, dict(detail="角色不存在")
        # 将user未在rids的角色信息status修改为1
        UserRoleModel.objects.filter(user=user).exclude(role_id=rid, status=9).update(
            status=1
        )
        # 将user的rid角色status修改为5
        UserRoleModel.objects.filter(user=user, role_id=rid).update(status=5)
        return 200, dict(detail="切换成功")


@api_controller("/auth", tags=["认证"])
class UserTokenController(TokenObtainSlidingController):

    @route.post("/login", response=UserTokenOutSchema, summary="登录", url_name="login")
    def obtain_token(self, data: LoginSchema):
        """
        获取用户对象并生成令牌
        Args:
            auth (LoginSchema): 用户登录信息
        Returns:
            UserTokenOutSchema: 包含用户对象、令牌和过期时间的输出对象
        """
        # 获取用户对象
        user_obj = authenticate(username=data.username, password=data.password)
        # 判断用户是否存在
        if not user_obj:
            raise APIException("用户名或密码错误")
        # 生成令牌
        token = SlidingToken.for_user(user_obj)
        return UserTokenOutSchema(
            user=user_obj,
            token=str(token),
            token_exp_date=datetime.fromtimestamp(token["exp"]),
        )

    @route.post(
        "/api-token-refresh",
        response=schema.TokenRefreshSlidingSerializer,
        summary="刷新token",
        url_name="refresh",
    )
    def refresh_token(self, refresh_token: schema.TokenRefreshSlidingSchema):
        """
        刷新令牌

        参数:
            refresh_token (schema.TokenRefreshSlidingSchema): 用于刷新令牌的参数对象

        返回:
            refresh (schema.TokenRefreshSlidingSchema): 刷新后的令牌对象
        """
        refresh = schema.TokenRefreshSlidingSerializer(**refresh_token.dict())
        return refresh


@api_controller(
    "/role", tags=["角色管理"], auth=JWTAuth(), permissions=[IsAuthenticated]
)
class RoleController(RoleViewMixin):
    @route.get(
        "",
        summary="角色列表",
        url_name="role-list",
        response=PageNumberPaginationExtra.get_response_schema(RoleSchema),
    )
    @paginate(PageNumberPaginationExtra, page_size=10)
    def get_role_list(
        self,
    ):
        return self.get_queryset()

    @route.get("/{rid}", response=RoleSchema, summary="角色信息", url_name="role-info")
    def get_role_info(self, rid: int):
        role = self.get_object_or_exception(
            self.get_queryset(),
            id=rid,
            error_message="角色不存在",
        )
        return role

    @route.post(
        "",
        response=[(200, RoleSchema), (400, MsgSchema)],
        summary="创建角色",
        url_name="create-role",
    )
    def create_role(self, data: RoleInSchema):
        """
        创建一个角色，并为其分配菜单权限。

        Args:
            data (RoleInSchema): 包含新角色信息的数据对象。

        Returns:
            tuple: 返回一个元组，第一个元素是状态码，第二个元素是返回的数据。
                状态码200表示成功创建角色并分配菜单权限，
                此时第二个元素是创建的角色对象；
                状态码400表示创建角色或分配菜单权限时出现异常，
                此时第二个元素是包含异常详细信息的字典。
        """
        mids = data.menus
        del data.menus
        try:
            role = data.create()
            RoleMenuModel.objects.bulk_create(
                [RoleMenuModel(role=role, menu_id=mid, status=5) for mid in mids]
            )
            return 200, role
        except Exception as ex:
            return 400, dict(detail=str(ex))

    @route.delete(
        "/{rid}", response={204: dict}, summary="删除角色", url_name="role-delete"
    )
    def delete_role(self, rid: int):
        role = self.get_object_or_exception(
            self.get_queryset(),
            id=rid,
            error_message="角色不存在",
        )
        role.delete()
        return self.create_response(
            "角色已删除", status_code=status.HTTP_204_NO_CONTENT
        )

    @route.generic(
        "/{rid}",
        methods=["PUT"],
        response=[(200, RoleSchema), (400, MsgSchema)],
        summary="修改角色",
        url_name="role-update",
    )
    def update_role(self, rid: int, data: RoleInSchema):
        mids = data.menus
        del data.menus
        role = self.get_object_or_exception(
            self.get_queryset(), id__exact=rid, error_message="角色不存在"
        )
        try:
            data.update(role)
             # 首先重置所有当前角色的菜单项状态
            RoleMenuModel.objects.filter(role=role).update(status=9)
            # 然后遍历要更新的菜单ID列表，只更新或创建存在的菜单项
            existing_menu_ids = RoleMenuModel.objects.filter(role=role).values_list(
                "menu_id", flat=True
            )
            menus_to_update = [mid for mid in mids if mid in existing_menu_ids]
            # 创建不存在的菜单项
            menus_to_create = [mid for mid in mids if mid not in existing_menu_ids]
            # 批量更新菜单项状态
            RoleMenuModel.objects.filter(role=role, menu_id__in=menus_to_update).update(
                status=5
            )
            # 批量创建菜单项
            RoleMenuModel.objects.bulk_create(
                [
                    RoleMenuModel(role=role, menu_id=mid, status=5)
                    for mid in menus_to_create
                ]
            )
            return 200, role
        except Exception as ex:
            return 400, dict(detail=str(ex))

    @route.get(
        "/{rid}/menu",
        response=[(200, DataSchema), (400, MsgSchema)],
        summary="查询角色拥有菜单",
        url_name="role-has-menu",
    )
    def role_has_menu(self, rid: int):
        menus = self.get_menus_by_role_id(rid)
        # 模型列表转换为字典列表
        menus_dict = [model_to_dict(menu,exclude=['creator','modifier']) for menu in menus]
        data = list_to_tree(menus_dict)
        try:
            return dict(data=data)
        except KeyError:
            return dict(detail="菜单缺少根节点")
